package ru.bsvk.ptpmapper;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by IntelliJ IDEA.
 * User: Ricoshet
 * Date: 25.11.12
 * Time: 0:14
 * To change this template use File | Settings | File Templates.
 */
public class PtpMapperImpl<F, T> implements PtpMapper<F, T> {
    /**
     * Field mapping of this class
     */
    Map<String, String> fieldMap;
    /**
     * List of immediate child mappers
     */
    List<PtpMapper<F, T>> mappers;
    Class<F> fromClass;
    Class<T> toClass;

    public PtpMapperImpl(Class fromClass, Class toClass) {
        fieldMap = new HashMap<String, String>();
        mappers = new ArrayList<PtpMapper<F, T>>();
        this.fromClass = fromClass;
        this.toClass = toClass;

    }


    @Override
    public T map(F from, T to) {
        try {
            //Define setters from T by mapping
            //Define getters from F by setters
            //
            //invoke setters with getters
            //
            Class fromMapClass = from.getClass();
            Class toMapClass = to.getClass();

            Method[] setters = findPojoSetters(toMapClass);
            for (Method curSetter : setters) {
                String getterFieldName = findMappingForSetter(getFieldNameFromSetter(curSetter.getName()), curSetter.getParameterTypes()[0]);
            }
//            Method[] methods = fromMapClass.getMethods();
//            for (Method fromMethod : methods) {
//                if (!isObjectMethod(fromMethod) && (fromMethod.getName().substring(0, 3).equals("get") ||
//                        fromMethod.getName().substring(0, 2).equals("is"))) {
//                    String methodName = fromMethod.getName();
//                    String fromFieldName = getFieldNameFromSetter(methodName);
//                    String toFieldName = fieldMap.get(fromFieldName);
//                    if (toFieldName == null)
//                        toFieldName = fromFieldName;
//                    String toMethodName = getSetterFromFieldName(toFieldName);
//                    //TODO: Лучше проверять наличие метода в классе, чем ловить эксепшн
//                    try {
//                        Method toMethod = toMapClass.getMethod(toMethodName, fromMethod.getReturnType());
//                        toMethod.invoke(to, new Object[]{fromMethod.invoke(from)});
//                    } catch (NoSuchMethodException e) {
//                    }
//                }
//            }
            return to;
        } catch (Exception e) {
            throw new RuntimeException("Ошибка маппинга! Подробности:", e);
        }
    }

    private String findMappingForSetter(String setterFieldName, Class<?> parameterType) {

        String fieldName = fieldMap.get(setterFieldName);
        if (fieldName != null) {
            return fieldName;
        }
        return null;
    }

    private Method[] findPojoSetters(Class toMapClass) {
        List<Method> resultMethods = new ArrayList<Method>();
        Method[] allMethods = toMapClass.getMethods();
        for (Method curMethod : allMethods) {
            if (isSetter(curMethod)) {
                resultMethods.add(curMethod);
            }
        }
        return resultMethods.toArray(new Method[0]);
    }

    @Override
    public void link(String fromField, String toField) {
        fieldMap.put(fromField, toField);
    }

    @Override
    public void addMapper(PtpMapper composedFieldMapper) {
        mappers.add(composedFieldMapper);
    }

    private boolean isObjectMethod(Method fromMethod) {
        String methodName = fromMethod.getName();
        if (!methodName.equals("getClass")) {
            return false;
        }
        return true;
    }

    private String getFieldNameFromSetter(String methodName) {
        boolean isIs = methodName.substring(0, 2).equals("is");
        if (isIs) {
            String lowFirstLetter = methodName.substring(2, 3).toLowerCase();
            methodName = lowFirstLetter + methodName.substring(3);
        } else {
            String lowFirstLetter = methodName.substring(3, 4).toLowerCase();
            methodName = lowFirstLetter + methodName.substring(4);
        }
        return methodName;
    }

    private String getSetterFromFieldName(String fieldName) {
        String correctMethodName = getCannonicalMethodName(fieldName);
        return correctMethodName;
    }

    private String getCannonicalMethodName(String fieldName) {
        String firstLetter = fieldName.substring(0, 1);
        String otherLetter = fieldName.substring(1);
        String correctMethodName = firstLetter.toUpperCase() + otherLetter;
        String fullMethodName = "set" + correctMethodName;
        return fullMethodName;
    }

    public boolean isGetter(Method method) {
        if (!method.getName().startsWith("get")) return false;
        if (method.getParameterTypes().length != 0) return false;
        if (void.class.equals(method.getReturnType())) return false;
        return true;
    }

    public boolean isSetter(Method method) {
        if (!method.getName().startsWith("set")) return false;
        if (method.getParameterTypes().length != 1) return false;
        return true;
    }

    public Map<String, String> getFieldMap() {
        return fieldMap;
    }

    public void setFieldMap(Map<String, String> fieldMap) {
        this.fieldMap = fieldMap;
    }

    public List<PtpMapper<F, T>> getMappers() {
        return mappers;
    }

    public void setMappers(List<PtpMapper<F, T>> mappers) {
        this.mappers = mappers;
    }

    public Class<F> getFromClass() {
        return fromClass;
    }

    public void setFromClass(Class<F> fromClass) {
        this.fromClass = fromClass;
    }

    public Class<T> getToClass() {
        return toClass;
    }

    public void setToClass(Class<T> toClass) {
        this.toClass = toClass;
    }
}
